﻿// AttributesImpl.java - default implementation of Attributes.
// http://www.saxproject.org
// Written by David Megginson
// NO WARRANTY!  This class is in the public domain.
// $Id$
using System;
using java = biz.ritter.javapi;

namespace org.xml.sax.helpers
{
    /**
     * Default implementation of the Attributes interface.
     *
     * <blockquote>
     * <em>This module, both source code and documentation, is in the
     * Public Domain, and comes with <strong>NO WARRANTY</strong>.</em>
     * See <a href='http://www.saxproject.org'>http://www.saxproject.org</a>
     * for further information.
     * </blockquote>
     *
     * <p>This class provides a default implementation of the SAX2
     * {@link org.xml.sax.Attributes Attributes} interface, with the 
     * addition of manipulators so that the list can be modified or 
     * reused.</p>
     *
     * <p>There are two typical uses of this class:</p>
     *
     * <ol>
     * <li>to take a persistent snapshot of an Attributes object
     *  in a {@link org.xml.sax.ContentHandler#startElement startElement} event; or</li>
     * <li>to construct or modify an Attributes object in a SAX2 driver or filter.</li>
     * </ol>
     *
     * <p>This class replaces the now-deprecated SAX1 {@link 
     * org.xml.sax.helpers.AttributeListImpl AttributeListImpl}
     * class; in addition to supporting the updated Attributes
     * interface rather than the deprecated {@link org.xml.sax.AttributeList
     * AttributeList} interface, it also includes a much more efficient 
     * implementation using a single array rather than a set of Vectors.</p>
     *
     * @since SAX 2.0
     * @author David Megginson
     * @version 2.0.1 (sax2r2)
     */
    public class AttributesImpl : Attributes
    {


        ////////////////////////////////////////////////////////////////////
        // Constructors.
        ////////////////////////////////////////////////////////////////////


        /**
         * Construct a new, empty AttributesImpl object.
         */
        public AttributesImpl()
        {
            length = 0;
            data = null;
        }


        /**
         * Copy an existing Attributes object.
         *
         * <p>This constructor is especially useful inside a
         * {@link org.xml.sax.ContentHandler#startElement startElement} event.</p>
         *
         * @param atts The existing Attributes object.
         */
        public AttributesImpl(Attributes atts)
        {
            setAttributes(atts);
        }



        ////////////////////////////////////////////////////////////////////
        // Implementation of org.xml.sax.Attributes.
        ////////////////////////////////////////////////////////////////////


        /**
         * Return the number of attributes in the list.
         *
         * @return The number of attributes in the list.
         * @see org.xml.sax.Attributes#getLength
         */
        public int getLength()
        {
            return length;
        }


        /**
         * Return an attribute's Namespace URI.
         *
         * @param index The attribute's index (zero-based).
         * @return The Namespace URI, the empty string if none is
         *         available, or null if the index is out of range.
         * @see org.xml.sax.Attributes#getURI
         */
        public String getURI(int index)
        {
            if (index >= 0 && index < length)
            {
                return data[index * 5];
            }
            else
            {
                return null;
            }
        }


        /**
         * Return an attribute's local name.
         *
         * @param index The attribute's index (zero-based).
         * @return The attribute's local name, the empty string if 
         *         none is available, or null if the index if out of range.
         * @see org.xml.sax.Attributes#getLocalName
         */
        public String getLocalName(int index)
        {
            if (index >= 0 && index < length)
            {
                return data[index * 5 + 1];
            }
            else
            {
                return null;
            }
        }


        /**
         * Return an attribute's qualified (prefixed) name.
         *
         * @param index The attribute's index (zero-based).
         * @return The attribute's qualified name, the empty string if 
         *         none is available, or null if the index is out of bounds.
         * @see org.xml.sax.Attributes#getQName
         */
        public String getQName(int index)
        {
            if (index >= 0 && index < length)
            {
                return data[index * 5 + 2];
            }
            else
            {
                return null;
            }
        }


        /**
         * Return an attribute's type by index.
         *
         * @param index The attribute's index (zero-based).
         * @return The attribute's type, "CDATA" if the type is unknown, or null
         *         if the index is out of bounds.
         * @see org.xml.sax.Attributes#getType(int)
         */
        public String getType(int index)
        {
            if (index >= 0 && index < length)
            {
                return data[index * 5 + 3];
            }
            else
            {
                return null;
            }
        }


        /**
         * Return an attribute's value by index.
         *
         * @param index The attribute's index (zero-based).
         * @return The attribute's value or null if the index is out of bounds.
         * @see org.xml.sax.Attributes#getValue(int)
         */
        public String getValue(int index)
        {
            if (index >= 0 && index < length)
            {
                return data[index * 5 + 4];
            }
            else
            {
                return null;
            }
        }


        /**
         * Look up an attribute's index by Namespace name.
         *
         * <p>In many cases, it will be more efficient to look up the name once and
         * use the index query methods rather than using the name query methods
         * repeatedly.</p>
         *
         * @param uri The attribute's Namespace URI, or the empty
         *        string if none is available.
         * @param localName The attribute's local name.
         * @return The attribute's index, or -1 if none matches.
         * @see org.xml.sax.Attributes#getIndex(java.lang.String,java.lang.String)
         */
        public int getIndex(String uri, String localName)
        {
            int max = length * 5;
            for (int i = 0; i < max; i += 5)
            {
                if (data[i].equals(uri) && data[i + 1].equals(localName))
                {
                    return i / 5;
                }
            }
            return -1;
        }


        /**
         * Look up an attribute's index by qualified (prefixed) name.
         *
         * @param qName The qualified name.
         * @return The attribute's index, or -1 if none matches.
         * @see org.xml.sax.Attributes#getIndex(java.lang.String)
         */
        public int getIndex(String qName)
        {
            int max = length * 5;
            for (int i = 0; i < max; i += 5)
            {
                if (data[i + 2].equals(qName))
                {
                    return i / 5;
                }
            }
            return -1;
        }


        /**
         * Look up an attribute's type by Namespace-qualified name.
         *
         * @param uri The Namespace URI, or the empty string for a name
         *        with no explicit Namespace URI.
         * @param localName The local name.
         * @return The attribute's type, or null if there is no
         *         matching attribute.
         * @see org.xml.sax.Attributes#getType(java.lang.String,java.lang.String)
         */
        public String getType(String uri, String localName)
        {
            int max = length * 5;
            for (int i = 0; i < max; i += 5)
            {
                if (data[i].equals(uri) && data[i + 1].equals(localName))
                {
                    return data[i + 3];
                }
            }
            return null;
        }


        /**
         * Look up an attribute's type by qualified (prefixed) name.
         *
         * @param qName The qualified name.
         * @return The attribute's type, or null if there is no
         *         matching attribute.
         * @see org.xml.sax.Attributes#getType(java.lang.String)
         */
        public String getType(String qName)
        {
            int max = length * 5;
            for (int i = 0; i < max; i += 5)
            {
                if (data[i + 2].equals(qName))
                {
                    return data[i + 3];
                }
            }
            return null;
        }


        /**
         * Look up an attribute's value by Namespace-qualified name.
         *
         * @param uri The Namespace URI, or the empty string for a name
         *        with no explicit Namespace URI.
         * @param localName The local name.
         * @return The attribute's value, or null if there is no
         *         matching attribute.
         * @see org.xml.sax.Attributes#getValue(java.lang.String,java.lang.String)
         */
        public String getValue(String uri, String localName)
        {
            int max = length * 5;
            for (int i = 0; i < max; i += 5)
            {
                if (data[i].equals(uri) && data[i + 1].equals(localName))
                {
                    return data[i + 4];
                }
            }
            return null;
        }


        /**
         * Look up an attribute's value by qualified (prefixed) name.
         *
         * @param qName The qualified name.
         * @return The attribute's value, or null if there is no
         *         matching attribute.
         * @see org.xml.sax.Attributes#getValue(java.lang.String)
         */
        public String getValue(String qName)
        {
            int max = length * 5;
            for (int i = 0; i < max; i += 5)
            {
                if (data[i + 2].equals(qName))
                {
                    return data[i + 4];
                }
            }
            return null;
        }



        ////////////////////////////////////////////////////////////////////
        // Manipulators.
        ////////////////////////////////////////////////////////////////////


        /**
         * Clear the attribute list for reuse.
         *
         * <p>Note that little memory is freed by this call:
         * the current array is kept so it can be 
         * reused.</p>
         */
        public void clear()
        {
            if (data != null)
            {
                for (int i = 0; i < (length * 5); i++)
                    data[i] = null;
            }
            length = 0;
        }


        /**
         * Copy an entire Attributes object.
         *
         * <p>It may be more efficient to reuse an existing object
         * rather than constantly allocating new ones.</p>
         * 
         * @param atts The attributes to copy.
         */
        public void setAttributes(Attributes atts)
        {
            clear();
            length = atts.getLength();
            if (length > 0)
            {
                data = new String[length * 5];
                for (int i = 0; i < length; i++)
                {
                    data[i * 5] = atts.getURI(i);
                    data[i * 5 + 1] = atts.getLocalName(i);
                    data[i * 5 + 2] = atts.getQName(i);
                    data[i * 5 + 3] = atts.getType(i);
                    data[i * 5 + 4] = atts.getValue(i);
                }
            }
        }


        /**
         * Add an attribute to the end of the list.
         *
         * <p>For the sake of speed, this method does no checking
         * to see if the attribute is already in the list: that is
         * the responsibility of the application.</p>
         *
         * @param uri The Namespace URI, or the empty string if
         *        none is available or Namespace processing is not
         *        being performed.
         * @param localName The local name, or the empty string if
         *        Namespace processing is not being performed.
         * @param qName The qualified (prefixed) name, or the empty string
         *        if qualified names are not available.
         * @param type The attribute type as a string.
         * @param value The attribute value.
         */
        public void addAttribute(String uri, String localName, String qName,
                      String type, String value)
        {
            ensureCapacity(length + 1);
            data[length * 5] = uri;
            data[length * 5 + 1] = localName;
            data[length * 5 + 2] = qName;
            data[length * 5 + 3] = type;
            data[length * 5 + 4] = value;
            length++;
        }


        /**
         * Set an attribute in the list.
         *
         * <p>For the sake of speed, this method does no checking
         * for name conflicts or well-formedness: such checks are the
         * responsibility of the application.</p>
         *
         * @param index The index of the attribute (zero-based).
         * @param uri The Namespace URI, or the empty string if
         *        none is available or Namespace processing is not
         *        being performed.
         * @param localName The local name, or the empty string if
         *        Namespace processing is not being performed.
         * @param qName The qualified name, or the empty string
         *        if qualified names are not available.
         * @param type The attribute type as a string.
         * @param value The attribute value.
         * @exception java.lang.ArrayIndexOutOfBoundsException When the
         *            supplied index does not point to an attribute
         *            in the list.
         */
        public void setAttribute(int index, String uri, String localName,
                      String qName, String type, String value)
        {
            if (index >= 0 && index < length)
            {
                data[index * 5] = uri;
                data[index * 5 + 1] = localName;
                data[index * 5 + 2] = qName;
                data[index * 5 + 3] = type;
                data[index * 5 + 4] = value;
            }
            else
            {
                badIndex(index);
            }
        }


        /**
         * Remove an attribute from the list.
         *
         * @param index The index of the attribute (zero-based).
         * @exception java.lang.ArrayIndexOutOfBoundsException When the
         *            supplied index does not point to an attribute
         *            in the list.
         */
        public void removeAttribute(int index)
        {
            if (index >= 0 && index < length)
            {
                if (index < length - 1)
                {
                    java.lang.SystemJ.arraycopy(data, (index + 1) * 5, data, index * 5,
                             (length - index - 1) * 5);
                }
                index = (length - 1) * 5;
                data[index++] = null;
                data[index++] = null;
                data[index++] = null;
                data[index++] = null;
                data[index] = null;
                length--;
            }
            else
            {
                badIndex(index);
            }
        }


        /**
         * Set the Namespace URI of a specific attribute.
         *
         * @param index The index of the attribute (zero-based).
         * @param uri The attribute's Namespace URI, or the empty
         *        string for none.
         * @exception java.lang.ArrayIndexOutOfBoundsException When the
         *            supplied index does not point to an attribute
         *            in the list.
         */
        public void setURI(int index, String uri)
        {
            if (index >= 0 && index < length)
            {
                data[index * 5] = uri;
            }
            else
            {
                badIndex(index);
            }
        }


        /**
         * Set the local name of a specific attribute.
         *
         * @param index The index of the attribute (zero-based).
         * @param localName The attribute's local name, or the empty
         *        string for none.
         * @exception java.lang.ArrayIndexOutOfBoundsException When the
         *            supplied index does not point to an attribute
         *            in the list.
         */
        public void setLocalName(int index, String localName)
        {
            if (index >= 0 && index < length)
            {
                data[index * 5 + 1] = localName;
            }
            else
            {
                badIndex(index);
            }
        }


        /**
         * Set the qualified name of a specific attribute.
         *
         * @param index The index of the attribute (zero-based).
         * @param qName The attribute's qualified name, or the empty
         *        string for none.
         * @exception java.lang.ArrayIndexOutOfBoundsException When the
         *            supplied index does not point to an attribute
         *            in the list.
         */
        public void setQName(int index, String qName)
        {
            if (index >= 0 && index < length)
            {
                data[index * 5 + 2] = qName;
            }
            else
            {
                badIndex(index);
            }
        }


        /**
         * Set the type of a specific attribute.
         *
         * @param index The index of the attribute (zero-based).
         * @param type The attribute's type.
         * @exception java.lang.ArrayIndexOutOfBoundsException When the
         *            supplied index does not point to an attribute
         *            in the list.
         */
        public void setType(int index, String type)
        {
            if (index >= 0 && index < length)
            {
                data[index * 5 + 3] = type;
            }
            else
            {
                badIndex(index);
            }
        }


        /**
         * Set the value of a specific attribute.
         *
         * @param index The index of the attribute (zero-based).
         * @param value The attribute's value.
         * @exception java.lang.ArrayIndexOutOfBoundsException When the
         *            supplied index does not point to an attribute
         *            in the list.
         */
        public void setValue(int index, String value)
        {
            if (index >= 0 && index < length)
            {
                data[index * 5 + 4] = value;
            }
            else
            {
                badIndex(index);
            }
        }



        ////////////////////////////////////////////////////////////////////
        // Internal methods.
        ////////////////////////////////////////////////////////////////////


        /**
         * Ensure the internal array's capacity.
         *
         * @param n The minimum number of attributes that the array must
         *        be able to hold.
         */
        private void ensureCapacity(int n)
        {
            if (n <= 0)
            {
                return;
            }
            int max;
            if (data == null || data.Length == 0)
            {
                max = 25;
            }
            else if (data.Length >= n * 5)
            {
                return;
            }
            else
            {
                max = data.Length;
            }
            while (max < n * 5)
            {
                max *= 2;
            }

            String[] newData = new String[max];
            if (length > 0)
            {
                java.lang.SystemJ.arraycopy(data, 0, newData, 0, length * 5);
            }
            data = newData;
        }


        /**
         * Report a bad array index in a manipulator.
         *
         * @param index The index to report.
         * @exception java.lang.ArrayIndexOutOfBoundsException Always.
         */
        private void badIndex(int index)//	throws ArrayIndexOutOfBoundsException
        {
            String msg =
                "Attempt to modify attribute at illegal index: " + index;
            throw new java.lang.ArrayIndexOutOfBoundsException(msg);
        }



        ////////////////////////////////////////////////////////////////////
        // Internal state.
        ////////////////////////////////////////////////////////////////////

        int length;
        String[] data;

    }

    // end of AttributesImpl.java

}